home *** CD-ROM | disk | FTP | other *** search
Text File | 1995-12-15 | 21.8 KB | 1,110 lines |
- /* MissileCommando.java - based on the arcade game Missile Command. */
-
- /*
- * Copyright (C) 1995 Mark Boyns <boyns@sdsu.edu>
- *
- * MissileCommando
- * <URL:http://www.sdsu.edu/~boyns/java/mc/>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
- import java.applet.*;
- import java.awt.*;
- import java.util.Enumeration;
- import java.util.Vector;
-
-
- /* Abstract class used for moving objects, etc. */
- abstract class Thing extends Thread
- {
- MissileCommando parent;
- public int X;
- public int Y;
- Color color;
-
- abstract void paint (Graphics g); /* paint the object */
- abstract void erase (Graphics g); /* erase the object */
- abstract boolean hit (Missile m); /* did this missile hit the object? */
- abstract void explode (); /* explode the object */
- }
-
-
- /* A simple rectangular base. This object just sits around waiting to
- explode. */
- class Base extends Thing
- {
- private int W = 0;
- private int H = 0;
-
- Base (MissileCommando parent, Color color, int x, int y, int w, int h)
- {
- this.parent = parent;
- X = x;
- Y = y;
- this.color = color;
-
- W = w;
- H = h;
-
- start ();
- }
-
- public void run ()
- {
- Thread.currentThread().setPriority (Thread.MIN_PRIORITY);
- waitToDie ();
- }
-
- public synchronized void waitToDie ()
- {
- try
- {
- wait ();
- }
- catch (InterruptedException e)
- {
- }
- }
-
- public synchronized void timeToDie ()
- {
- notify ();
- }
-
- public boolean hit (Missile m)
- {
- return (m.X >= X && m.X <= (X + W)
- && m.Y >= Y && m.Y <= (Y + H));
- }
-
- public void explode ()
- {
- timeToDie ();
- stop ();
- }
-
- public void paint (Graphics g)
- {
- g.setColor (color);
- g.fillRect (X, Y, W, H);
- }
-
- public void erase (Graphics g)
- {
- g.setColor (Color.lightGray);
- g.fillRect (X, Y, W, H);
- }
- }
-
-
- /* A missile which moves from X1,Y1 to X2,Y2 with velocity V. A
- missile can randomly replicate itself into 3 missiles. */
- class Missile extends Thing
- {
- private boolean paintme = false;
- private int X1 = 0;
- private int Y1 = 0;
- private int X2 = 0;
- private int Y2 = 0;
- private int V = 0;
- private float m = 0;
- private float b = 0;
- private float x = 0;
- private float y = 0;
- private boolean replicate = false;
-
- Missile (MissileCommando parent, Color color, int x1, int y1, int x2, int y2, int v)
- {
- this.parent = parent;
- X = X1;
- Y = Y1;
- this.color = color;
-
- X1 = x1;
- Y1 = y1;
- X2 = x2;
- Y2 = y2;
- V = v;
- start ();
- }
-
- public void run ()
- {
- Thread.currentThread().setPriority (Thread.MIN_PRIORITY);
-
- m = (float) (Y2 - Y1) / (X2 - X1);
- b = Y1 - (m*X1);
-
- x = X1;
- y = Y1;
-
- /* 10% chance this missile will replicate. */
- replicate = Math.random () > 0.90;
- if (replicate)
- {
- color = Color.magenta;
- }
-
- while (y <= Y2)
- {
- /* 25% chance this missile will replicate now. */
- if (replicate && y > Y1+5*V && Math.random () > 0.75)
- {
- for (int i = 0; i < 3; i++)
- {
- parent.createMissile (X, Y, V);
- }
- /* stop the current missile */
- break;
- }
-
- paintme = true;
- parent.repaint ();
-
- try
- {
- Thread.sleep (100);
- }
- catch (InterruptedException e)
- {
- }
- }
-
- paintme = true;
- parent.repaint ();
- }
-
- public boolean hit (Missile m)
- {
- return m.X == X && m.Y == Y;
- }
-
- public void explode ()
- {
- stop ();
- }
-
- public void paint (Graphics g)
- {
- if (paintme)
- {
- g.setColor (Color.lightGray);
- g.drawLine (X1, Y1, X, Y);
-
- y += V;
- x = (y - b) / m;
-
- X = (int)x;
- Y = (int)y;
-
- g.setColor (color);
- g.drawLine (X1, Y1, X, Y);
-
- paintme = false;
- }
- }
-
- public void erase (Graphics g)
- {
- g.setColor (Color.lightGray);
- g.drawLine (X1, Y1, X, Y);
- }
- }
-
-
- /* Generic explosion which draws a circle that grows and shrinks. The
- explosion is draw at X,Y and has a maximum size of S. */
- class Explosion extends Thing
- {
- private boolean paintme = false;
- private int S = 0;
- public int size = 0;
- private int scale = 0;
-
- Explosion (MissileCommando parent, Color color, int x, int y, int s)
- {
- this.parent = parent;
- X = x;
- Y = y;
- this.color = color;
-
- S = s;
- start ();
- }
-
- public void run ()
- {
- Thread.currentThread().setPriority (Thread.MIN_PRIORITY);
-
- /* grow */
- scale = 10;
- size = 1;
- do
- {
- paintme = true;
- parent.repaint ();
-
- try
- {
- Thread.sleep (50);
- }
- catch (InterruptedException e)
- {
- }
- }
- while (size <= S);
-
- /* shrink */
- scale = -10;
- do
- {
- paintme = true;
- parent.repaint ();
- try
- {
- Thread.sleep (100);
- }
- catch (InterruptedException e)
- {
- }
- }
- while (size >= 0);
-
- paintme = true;
- parent.repaint ();
- }
-
- public boolean hit (Missile m)
- {
- return (m.X >= (X - size/2) && m.X <= (X + size/2)
- && m.Y >= (Y - size/2) && m.Y <= (Y + size/2));
- }
-
- public void explode ()
- {
- }
-
- public void paint (Graphics g)
- {
- if (paintme)
- {
- if (scale < 0)
- {
- g.setColor (Color.lightGray);
- g.fillOval (X - size/2, Y - size/2, size, size);
- }
-
- size += scale;
-
- g.setColor (color);
- g.fillOval (X - size/2, Y - size/2, size, size);
- paintme = false;
- }
- }
-
- public void erase (Graphics g)
- {
- }
- }
-
-
- /* A shot explosion base on Explosion. */
- class ShotExplosion extends Explosion
- {
- ShotExplosion (MissileCommando parent, int x, int y)
- {
- super (parent, Color.black, x, y, 60);
- }
- }
-
-
- /* A base explosion base on Explosion. */
- class BaseExplosion extends Explosion
- {
- BaseExplosion (MissileCommando parent, int x, int y)
- {
- super (parent, Color.red, x, y, 100);
- }
- }
-
-
- /* Draw a blinking message. */
- class Message extends Thing
- {
- private String message;
- private int blinks;
- private int delay;
- private int blinkCount;
-
- Message (MissileCommando parent, String message, int x, int y, int blinks, int delay)
- {
- this.parent = parent;
- this.message = message;
- X = x;
- Y = y;
- this.blinks = blinks;
- this.delay = delay;
- color = Color.black;
-
- start ();
- }
-
- Message (MissileCommando parent, String message, int x, int y, int blinks)
- {
- this (parent, message, x, y, blinks, 500);
- }
-
- Message (MissileCommando parent, String message, int x, int y)
- {
- this (parent, message, x, y, 3, 500);
- }
-
- public void run ()
- {
- Thread.currentThread().setPriority (Thread.MIN_PRIORITY);
-
- for (blinkCount = 0; blinkCount < 2*blinks; blinkCount++)
- {
- parent.repaint ();
- try
- {
- Thread.sleep (delay);
- }
- catch (InterruptedException e)
- {
- }
- }
-
- parent.repaint ();
- }
-
- public void paint (Graphics g)
- {
- if ((blinkCount % 2) == 0)
- {
- g.setColor (color);
- }
- else
- {
- g.setColor (Color.lightGray);
- }
- g.drawString (message, X, Y);
- }
-
- public void erase (Graphics g)
- {
- g.setColor (Color.lightGray);
- g.drawString (message, X, Y);
- }
-
- public boolean hit (Missile m)
- {
- return false;
- }
-
- public void explode ()
- {
- }
- }
-
-
- /* Semaphore class used to synchronize threads. */
- class Semaphore
- {
- private boolean taken;
-
- Semaphore ()
- {
- taken = false;
- }
-
- Semaphore (boolean taken)
- {
- this.taken = taken;
- }
-
- public synchronized void take ()
- {
- while (taken)
- {
- try
- {
- wait ();
- }
- catch (Exception e)
- {
- }
- }
- taken = true;
- }
-
- public synchronized boolean peek ()
- {
- return taken;
- }
-
- public synchronized void give ()
- {
- taken = false;
- notify ();
- }
- }
-
-
- /* The main applet. */
- public class MissileCommando extends java.applet.Applet implements Runnable
- {
- /* Screen sizes. */
- private final int SCORE_WIDTH = 70;
- private final int WORLD_WIDTH = 430;
- private final int WORLD_HEIGHT = 300;
-
- /* Base parameters. */
- private final int BASES = 5;
- private final int BASE_SPACING = 30;
- private final int BASE_WIDTH = 50;
- private final int BASE_HEIGHT = 40;
-
- /* Points */
- private final int POINTS_MISSILE = 100;
- private final int POINTS_EXTRA_SHOTS = 50;
- private final int POINTS_BASE = 100;
- private final int POINTS_NEW_BASE = 5000;
-
- /* Limit the number of concurrent shots. */
- private final int MAX_SHOTS = 10;
- private int shots = 0;
-
- /* The sounds. */
- private AudioClip startSound = null;
- private AudioClip applauseSound = null;
- private AudioClip missileSound = null;
- private AudioClip shotExplosionSound = null;
- private AudioClip missileExplosionSound = null;
- private AudioClip baseExplosionSound = null;
- private AudioClip music = null;
-
- /* State variables. */
- private boolean playing = false;
- private boolean clearScreen = false;
- private boolean loadingSounds = false;
-
- /* Current game parameters. */
- private int score = 0;
- private int level = 0;
- private int speed = 0;
- private int delay = 0;
- private int missileCount = 0;
- private int shotCount = 0;
-
- /* Synchronization semaphores. */
- private Semaphore missileSemaphore;
- private Semaphore messageSemaphore;
-
- /* Fonts */
- private Font font;
- private FontMetrics fontMetrics;
-
- /* Strings */
- private String scoreString = "Score";
- private String levelString = "Level";
- private String shotString = "Shots";
- private String welcomeString = "Click to start";
- private String loadingString = "Loading sounds...";
-
- /* Vector used to keep track of all moving objects (Things). */
- private Vector things;
-
- /* The main applet's thread. */
- private Thread thread = null;
-
-
- /* Initialize the applet. */
- public void init ()
- {
- font = new Font ("TimesRoman", Font.BOLD, 24);
- fontMetrics = getFontMetrics (font);
- setFont (font);
- resize (WORLD_WIDTH + SCORE_WIDTH, WORLD_HEIGHT);
- }
-
-
- /* Start a new game. */
- public synchronized void newGame ()
- {
- playing = true;
- clearScreen = true;
-
- stopThreads ();
-
- thread = new Thread (this);
- thread.start ();
- }
-
-
- /* The game engine. */
- public void run ()
- {
- int n;
- int newBases = 0;
-
- Thread.currentThread().setPriority (Thread.MIN_PRIORITY);
-
- score = 0;
- shotCount = 0;
- shots = 0;
-
- things = new Vector (32);
- missileSemaphore = new Semaphore ();
- messageSemaphore = new Semaphore ();
-
- getSounds ();
- createBases ();
-
- if (music != null)
- {
- music.loop ();
- }
-
- for (level = 1; ; level++)
- {
- if (level == 1)
- {
- if (startSound != null)
- {
- startSound.play ();
- }
- }
- else
- {
- if (applauseSound != null)
- {
- applauseSound.play ();
- }
- }
- messageSemaphore.take ();
- createMessage ("Level " + level);
- messageSemaphore.take ();
- messageSemaphore.give ();
-
- /* Missile speed. */
- speed = 5 + (level - 1);
- if (speed > (WORLD_HEIGHT / 10))
- {
- speed = WORLD_HEIGHT / 10;
- }
-
- /* Delay between missiles. */
- delay = 2000 - ((level-1)*200);
- if (delay < 500)
- {
- delay = 500;
- }
-
- /* Number of missiles. */
- missileCount = 5 + ((level-1)*5);
-
- /* Number of shots. */
- shotCount = missileCount * 2;
-
- /* Wait until missiles are ready. */
- missileSemaphore.take ();
-
- while (missileCount > 0)
- {
- try
- {
- Thread.sleep (delay);
- }
- catch (InterruptedException e)
- {
- }
-
- /* Fire a missile. */
- createMissile (speed);
- missileCount--;
- }
-
- /* Wait until missiles are dead. */
- missileSemaphore.take ();
- missileSemaphore.give ();
-
- /* See if any new bases can be created. */
- n = score - newBases*POINTS_NEW_BASE;
- while (countBases () < BASES && n >= POINTS_NEW_BASE)
- {
- createBase ();
- n -= POINTS_NEW_BASE;
- newBases++;
- }
-
- /* Game over? */
- n = countBases ();
- if (n == 0)
- {
- break;
- }
- else
- {
- score += n * POINTS_BASE;
- }
- }
-
- playing = false;
-
- if (music != null)
- {
- music.stop ();
- }
-
- /* Destroy the world! */
- for (int i = 0; i < 5; i++)
- {
- int x = (int) (Math.random () * WORLD_WIDTH);
- int y = (int) (Math.random () * WORLD_HEIGHT);
- things.addElement (new Explosion (this, Color.red, x, y, 500));
- }
-
- messageSemaphore.take ();
- createMessage ("GAME OVER");
- messageSemaphore.take ();
- messageSemaphore.give ();
- }
-
-
- /* Stop all running threads. */
- public void stopThreads ()
- {
- if (things != null)
- {
- Enumeration e;
- e = things.elements ();
- while (e.hasMoreElements ())
- {
- Thing thing = (Thing) e.nextElement ();
- thing.explode ();
- }
- }
- }
-
-
- /* Stop this applet. */
- public void stop ()
- {
- if (music != null)
- {
- music.stop ();
- }
-
- stopThreads ();
-
- if (thread != null)
- {
- thread.stop ();
- thread = null;
- }
- }
-
-
- /* Get all the sounds. */
- void getSounds ()
- {
- loadingSounds = true;
- repaint ();
-
- startSound = getAudioClip (getCodeBase (), "sounds/sub_dive_horn.au");
- applauseSound = getAudioClip (getCodeBase (), "sounds/applause.au");
- missileSound = getAudioClip (getCodeBase (), "sounds/missile.au");
- shotExplosionSound = getAudioClip (getCodeBase (), "sounds/shot.au");
- missileExplosionSound = getAudioClip (getCodeBase (), "sounds/beep_multi.au");
- baseExplosionSound = getAudioClip (getCodeBase (), "sounds/bzzzt.au");
- /* background music is disabled since the sound file is too
- large. */
- music = null; /* getAudioClip (getCodeBase (), "sounds/pink_panther.au"); */
- loadingSounds = false;
- }
-
-
- /* Create the bases. */
- void createBases ()
- {
- for (int i = 0; i < BASES; i++)
- {
- createBase ();
- }
- }
-
-
- /* Create one base, if possible. */
- void createBase ()
- {
- Enumeration e;
- boolean found;
- int i, x;
-
- for (i = 0, x = BASE_SPACING; i < BASES; i++, x += BASE_WIDTH + BASE_SPACING)
- {
- found = false;
- e = things.elements ();
- while (e.hasMoreElements ())
- {
- Thing thing = (Thing) e.nextElement ();
- if (thing instanceof Base && thing.X == x)
- {
- found = true;
- }
- }
-
- if (!found)
- {
- things.addElement (new Base (this, Color.blue, x,
- WORLD_HEIGHT - BASE_HEIGHT - 1,
- BASE_WIDTH,
- BASE_HEIGHT));
- break;
- }
- }
- }
-
-
- /* Create a shot explosion at x,y. */
- void createShotExplosion (int x, int y)
- {
- if (shots > MAX_SHOTS)
- {
- return;
- }
-
- things.addElement (new ShotExplosion (this, x, y));
- if (shotExplosionSound != null)
- {
- shotExplosionSound.play ();
- }
-
- shots++;
- }
-
-
- /* Create a base explosion at x,y. */
- void createBaseExplosion (int x, int y)
- {
- things.addElement (new BaseExplosion (this, x, y));
- if (baseExplosionSound != null)
- {
- baseExplosionSound.play ();
- }
- }
-
-
- /* Create a missile starting at x,y with speed. */
- void createMissile (int x1, int y1, int speed)
- {
- int x2, y2;
- Color color = Color.red;
-
- x2 = (int)(Math.random () * WORLD_WIDTH);
- if (x2 == 0) x2 = 1;
- y2 = WORLD_HEIGHT - 2;
-
- things.addElement (new Missile (this, color, x1, y1, x2, y2, speed));
- if (missileSound != null)
- {
- missileSound.play ();
- }
- }
-
-
- /* Create a missile at a random location with speed. */
- void createMissile (int speed)
- {
- int x1, y1;
-
- x1 = (int)(Math.random () * WORLD_WIDTH);
- if (x1 == 0) x1 = 1;
- y1 = 2;
-
- createMissile (x1, y1, speed);
- }
-
-
- /* Create a blinking message. */
- void createMessage (String string)
- {
- int w = fontMetrics.stringWidth (string);
- int x = WORLD_WIDTH/2 - w/2;
- int y = WORLD_HEIGHT/2;
-
- things.addElement (new Message (this, string, x, y));
- }
-
-
- /* Handle events. */
- public boolean handleEvent (Event e)
- {
- if (e.id == Event.MOUSE_DOWN) /* mouse click */
- {
- if (playing
- && shotCount > 0
- && e.x >= 0 && e.x <= WORLD_WIDTH
- && e.y >= 0 && e.y <= WORLD_HEIGHT)
- {
- createShotExplosion (e.x, e.y);
- shotCount--;
- }
- else if (!playing)
- {
- newGame ();
- }
- return true;
- }
- else
- {
- return super.handleEvent (e);
- }
- }
-
-
- /* Check all things for a collision with this missile. */
- public void checkCollision (Missile missile)
- {
- Enumeration e;
- int nbases = 0, nexplosions = 0;
-
- e = things.elements ();
- while (e.hasMoreElements ())
- {
- Thing thing = (Thing) e.nextElement ();
- if (thing != missile && thing.isAlive ())
- {
- if (thing.hit (missile))
- {
- thing.explode ();
-
- if (thing instanceof Base)
- {
- nbases++;
- }
- else if (thing instanceof ShotExplosion)
- {
- nexplosions++;
- score += POINTS_MISSILE;
- if (missileExplosionSound != null)
- {
- missileExplosionSound.play ();
- }
- }
- }
- }
- }
-
- if (nbases > 0)
- {
- createBaseExplosion (missile.X, missile.Y);
- }
-
- if (nbases > 0 || nexplosions > 0)
- {
- missile.explode ();
- }
- }
-
-
- /* Update (paint, erase) things. */
- public synchronized void updateThings (Graphics g)
- {
- int i, j;
- Enumeration e;
-
- e = things.elements ();
- while (e.hasMoreElements ())
- {
- Thing thing = (Thing) e.nextElement ();
- if (thing.isAlive ())
- {
- thing.paint (g);
-
- if (thing instanceof Missile)
- {
- checkCollision ((Missile) thing);
- }
- }
- else
- {
- thing.erase (g);
- things.removeElement (thing);
-
- if (thing instanceof Message)
- {
- messageSemaphore.give ();
- }
- else if (thing instanceof ShotExplosion)
- {
- shots--;
- }
- }
- }
- }
-
-
- /* Return the number of remaining missiles. */
- public int countMissiles ()
- {
- Enumeration e;
- int count = 0;
-
- e = things.elements ();
- while (e.hasMoreElements ())
- {
- Thing thing = (Thing) e.nextElement ();
- if (thing instanceof Missile && thing.isAlive ())
- {
- count++;
- }
- }
-
- return count;
- }
-
-
- /* Return the number of remaining bases. */
- public int countBases ()
- {
- Enumeration e;
- int count = 0;
-
- e = things.elements ();
- while (e.hasMoreElements ())
- {
- Thing thing = (Thing) e.nextElement ();
- if (thing instanceof Base && thing.isAlive ())
- {
- count++;
- }
- }
-
- return count;
- }
-
-
- /* Draw the borders. */
- public void updateBorder (Graphics g)
- {
- g.setColor (Color.black);
- g.drawRect (0, 0, WORLD_WIDTH + SCORE_WIDTH - 1, WORLD_HEIGHT - 1);
- g.drawLine (WORLD_WIDTH, 0, WORLD_WIDTH, WORLD_HEIGHT);
- }
-
-
- String numberToZeroPaddedString (int number, int length)
- {
- StringBuffer s;
-
- s = new StringBuffer (Integer.toString (number));
- while (s.length () < length)
- {
- s.insert (0, "0");
- }
-
- return s.toString ();
- }
-
-
- /* Draw the score. */
- public synchronized void updateScore (Graphics g)
- {
- int h, w;
- int x, y;
- int n;
- StringBuffer s;
-
- w = fontMetrics.stringWidth ("00000");
- h = fontMetrics.getHeight ();
- x = WORLD_WIDTH + 5;
- g.setColor (Color.black);
-
- y = h;
- g.drawString (scoreString, x, y);
-
- y += h;
- g.clearRect (x, y - h, w, h);
- g.drawString (numberToZeroPaddedString (score, 5), x, y);
-
- y += 2*h;
- g.drawString (shotString, x, y);
-
- y += h;
- g.clearRect (x, y - h, w, h);
- g.drawString (numberToZeroPaddedString (shotCount, 5), x, y);
- }
-
-
- /* Don't clear the screen; call paint. */
- public void update (Graphics g)
- {
- paint (g);
- }
-
-
- /* Paint the screen. */
- public void paint (Graphics g)
- {
- if (clearScreen)
- {
- g.setColor (Color.lightGray);
- g.fillRect (0, 0, WORLD_WIDTH + SCORE_WIDTH, WORLD_HEIGHT);
- clearScreen = false;
- }
-
- if (loadingSounds)
- {
- int w = fontMetrics.stringWidth (loadingString);
- int x = WORLD_WIDTH/2 - w/2;
- int y = WORLD_HEIGHT/2;
- g.setColor (Color.black);
- g.drawString (loadingString, x, y);
- clearScreen = true;
- }
- else if (!playing && (things == null || things.size () == 0))
- {
- int w = fontMetrics.stringWidth (welcomeString);
- int x = WORLD_WIDTH/2 - w/2;
- int y = WORLD_HEIGHT/2;
- g.setColor (Color.black);
- g.drawString (welcomeString, x, y);
- }
- else if (things != null)
- {
- Graphics gc = g.create (0, 0, WORLD_WIDTH, WORLD_HEIGHT);
- updateThings (gc);
-
- /* No more missiles. */
- if (missileSemaphore != null && countMissiles () == 0 && missileCount == 0)
- {
- missileSemaphore.give ();
- }
- }
-
- updateBorder (g);
- updateScore (g);
- }
- }
-
- /*
- Local variables:
- eval: (progn (make-local-variable 'compile-command) (setq compile-command (concat "javac " buffer-file-name)))
- End:
- */
-